/*
 *  MutaSim - an artificial life simulator based on cellular automata.
 *  Copyright (C) 2009-2010 Alexei Tereschenko <aytereschenko@gmail.com>
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package tilex.sim;

import java.util.Random;


/**
 */
public class Field {

	private static Random	rnd					= new Random();

	/**
	 * Ширина поля.
	 */
	private final int		width;
	/**
	 * Высота поля.
	 */
	private final int		height;
	/**
	 * Кол-во видов ресурсов на поле.
	 */
	private final int		resourceTypes;
	/**
	 * Матрица организмов.
	 */
	private Creature[]		creatures			= null;
	/**
	 * Матрицы с ресурсами всех видов.
	 * Первый индекс - вид ресурса, второй - матрица данного вида ресурсов.
	 */
	private int[][]			resources			= null;		// ресурсы:
	/**
	 * Способ обработки "краевого эффекта".
	 * 0 - поле замкнуто по краям
	 * 1 - поле замкнуто в тороид
	 */
	private int				edgeType;
	/**
	 * Смещения в матрице creatures до 8 окружающих клетку соседей и
	 * аналогичные смещения по координатам для обработки краевых эффектов.
	 */
	private int[]			neighborOffsets		= null;
	private int[][]			neighborXYOffsets	= null;

	Field(int width, int height, int resourceTypes) {
		this.width = width;
		this.height = height;
		this.resourceTypes = resourceTypes;
		Creature.setResourceTypes(resourceTypes);

		creatures = new Creature[width * height];
		resources = new int[resourceTypes][width * height];
		// TODO: после отладки - удалить
		for (int i = 0; i < resourceTypes; i++) {
			for (int j = 0; j < width * height; j++) {
				resources[i][j] = 10 + rnd.nextInt(20);
			}
		}
		neighborOffsets = new int[] {-1, width + 1, -width, width - 1, 1, width, -width + 1, -width - 1};
		neighborXYOffsets = new int[][] { {-1, 0}, {1, 1}, {0, -1}, {-1, 1}, {1, 0}, {0, 1}, {1, -1}, {-1, -1}};
		setEdgeType(0);
	}

	public int getResourceCount(int type) {
		int sum = 0;
		if (type < resourceTypes) {
			for (int i = 0; i < width * height; i++) {
				sum += resources[type][i];
			}
		}
		return sum;
	}

	public int getTotalCreatureMass() {
		int sum = 0;
		for (int i = 0; i < width * height; i++) {
			if (creatures[i] != null) {
				sum += creatures[i].getMass();
			}
		}
		return sum;
	}

	private void shuffleNeighborOffsets() {
		for (int i = 0; i < 8; i++) {
			int j = i + rnd.nextInt(8 - i);
			int t = neighborOffsets[i];
			neighborOffsets[i] = neighborOffsets[j];
			neighborOffsets[j] = t;
			t = neighborXYOffsets[i][0];
			neighborXYOffsets[i][0] = neighborXYOffsets[j][0];
			neighborXYOffsets[j][0] = t;
			t = neighborXYOffsets[i][1];
			neighborXYOffsets[i][1] = neighborXYOffsets[j][1];
			neighborXYOffsets[j][1] = t;
		}
	}

	public int getWidth() {
		return width;
	}

	public int getHeight() {
		return height;
	}

	public int getResourceTypes() {
		return resourceTypes;
	}

	public void setEdgeType(int edgeType) {
		this.edgeType = edgeType;
	}

	public int getEdgeType() {
		return edgeType;
	}

	void randomize(int n) {
		Random rnd = new Random();
		for (int r = 0; r < resourceTypes; r++) {
			for (int i = 0; i < width * height; i++) {
				resources[r][i] = rnd.nextInt(n);
			}
		}
	}

	int getResource(int type, int x, int y) {
		if (resources != null)
			return resources[type][y * width + x];
		else
			return 0;
	}

	public Creature getCreature(int x, int y) {
		if (creatures != null)
			return creatures[y * width + x];
		else
			return null;
	}

	/**
	 * Добавляет организм на поле в указанные координаты.
	 * 
	 * @param x
	 *            координата x
	 * @param y
	 *            координат y
	 * @param sample
	 *            образец добавляемого организма
	 */
	public void addCreature(int x, int y, CreatureSample sample) {
		if (creatures != null) {
			Creature c = new Creature(sample, false);
			creatures[y * width + x] = c;
			// FIXME: убедиться, что ресурсы не будут < 0
			resources[c.getResourceIn()][y * width + x] -= c.getMass();
		}
	}

	/**
	 * Добавляет новорожденный организм в одну из восьми соседних клеток относительно заданной.
	 * 
	 * @param x
	 *            координата x
	 * @param y
	 *            координата y
	 * @param i
	 *            смещение клетки x,y на поле (чтобы лишний раз не вычислять)
	 * @param offs
	 *            текущее смещение в массиве neighbor..Offsets
	 * @param creature
	 *            новорожденный
	 */
	public void placeNewBornCreature(int x, int y, int i, int offs, Creature creature) {
		// смещение на поле для новорожденного
		int j = -1;
		// если клетка не на краю, обрабатываем обычным образом
		if (y > 0 && y < height - 1 && x > 0 && x < width - 1) {
			j = i + neighborOffsets[offs];
		}
		// если клетка на краю, обрабатываем с учетом краевого эффекта
		else {
			int nx = x + neighborXYOffsets[offs][0];
			int ny = y + neighborXYOffsets[offs][1];

			// обрабатываем краевой эффект
			switch (edgeType) {
				// замкнутые края
				case 0:
					if (!(nx < 0 || nx >= width || ny < 0 || ny >= height)) {
						j = ny * width + nx;
					}
					break;
				// тороид
				case 1:
					if (nx < 0) {
						nx = width - 1;
					} else if (nx >= width) {
						nx = 0;
					}
					if (ny < 0) {
						ny = height - 1;
					} else if (ny >= height) {
						ny = 0;
					}
					j = ny * width + nx;
					break;
			}
		}
		// если вышли за пределы поля, то возвращаем ресурсы на место родителя
		if (j < 0) {
			resources[creature.getResourceOut()][i] += creature.getMass();
		} else {
			if (creatures[j] == null || creatures[j].getNewBornSeed() != -1
							&& creatures[j].getNewBornSeed() < creature.getNewBornSeed()) {
				// если мы убиваем более слабый организм, то надо вернуть его ресурсы на поле
				if (creatures[j] != null) {
					resources[creatures[j].getResourceOut()][j] += creatures[j].getMass();
				}
				creatures[j] = creature;
			} else {
				// если дитё родиться не смогло, надо вернуть ресурсы на поле
				resources[creature.getResourceOut()][j] += creature.getMass();
			}
		}
	}

	/**
	 * Выполняет один шаг симуляции на поле.
	 */
	public void simulate() {
		if (creatures != null) {
			int i = 0;
			for (int y = 0; y < height; y++) {
				for (int x = 0; x < width; x++, i++) {
					if (creatures[i] != null) {
						simulateCreature(x, y, i);
					}
				}
			}
		}
	}

	/**
	 * Выполняет симуляцию для одного организма в указанных координатах на поле.
	 * 
	 * @param x
	 *            координата x
	 * @param y
	 *            координата y
	 * @param i
	 *            смещение в массиве creatures для текущего организма (для уменьшения кол-ва вычислений)
	 */
	private void simulateCreature(int x, int y, int i) {
		Creature c = creatures[i];
		int r = c.simulate(resources[c.getResourceIn()][i]);
		if (c.isDead()) {
			// если умер, возвращаем ресурсы на поле
			resources[c.getResourceOut()][i] += c.getMass();
			creatures[i] = null;
		} else {
			// если живой, забираем с поля съеденные ресурсы
			resources[c.getResourceIn()][i] -= r;
			// пробуем рожать
			if (c.canReproduct()) {
				shuffleNeighborOffsets();
				int offs = 0;
				while (c.canReproduct()) {
					Creature newBorn = c.reproduct();
					placeNewBornCreature(x, y, i, offs++ % 8, newBorn);
				}
			}
		}
	}
}
